import { effect } from "../reactivity/effect";
import { ShapeFlags } from "../shared/ShapeFlags";
import { EMPTY_OBJ, isObject } from "../shared/index";
import { createComponentInstance, setupComponent } from "./component";
import { shouldUpdateComponent } from "./componentUpdateUtils";
import { createAppAPI } from "./createApp";
import { queueJobs } from "./scheduler";
import { Fragement, Text } from "./vnode";

export function createRenderer(options) {
    // 自定义渲染器选项接口
    const {
        createElement: hostCreateElement,
        patchProp: hostPatchProp,
        insert: hostInsert,
        remove: hostRemove,
        setElementText: hostSetElementText
    } = options

    function render(vnode, container) {
        patch(null, vnode, container, null, null)
    }

    // oldVnode老节点
    // newVnode新节点
    function patch(oldVnode, newVnode, container, parentComponent, anchor) {
        // 判断vnode是不是一个element
        const { type, shapeFlag } = newVnode;

        // Fragement -> 只渲染children
        switch (type) {
            case Fragement:
                processFragment(oldVnode, newVnode, container, parentComponent, anchor);
                break;
            case Text:
                processText(oldVnode, newVnode, container);
                break;

            default:
                if (shapeFlag & ShapeFlags.ELEMENT) {
                    processElement(oldVnode, newVnode, container, parentComponent, anchor)
                } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
                    // 处理组件
                    processComponent(oldVnode, newVnode, container, parentComponent, anchor);
                }
                break;
        }

    }

    function processFragment(oldVnode, newVnode, container, parentComponent, anchor) {
        mountChildren(newVnode.children, container, parentComponent, anchor);
    }

    function processText(oldVnode, newVnode, container) {
        const { children } = newVnode;
        const textNode = (newVnode.el = document.createTextNode(children));
        container.append(textNode);
    }

    function processElement(oldVnode, newVnode, container, parentComponent, anchor) {
        if (!oldVnode) {
            mountElement(newVnode, container, parentComponent, anchor);
        } else {
            patchElement(oldVnode, newVnode, container, parentComponent, anchor);
        }
    }

    function patchElement(oldVnode, newVnode, container, parentComponent, anchor) {
        console.log("patchElement");
        console.log("oldVnode", oldVnode);
        console.log("newVnode", newVnode);
        console.log("container", container);
        console.log("parentComponent", parentComponent);


        // 属性修改操作
        const oldProps = oldVnode.props || EMPTY_OBJ;
        const newProps = newVnode.props || EMPTY_OBJ;
        const el = (newVnode.el = oldVnode.el);
        patchProps(el, oldProps, newProps);

        // 子节点修改操作
        patchChildren(oldVnode, newVnode, el, parentComponent, anchor);
    }

    function patchChildren(oldVnode, newVnode, container, parentComponent, anchor) {
        const prevShapeFlag = oldVnode.shapeFlag;
        const oldVnodeChildren = oldVnode.children;
        const { shapeFlag } = newVnode;
        const newVnodeChildren = newVnode.children;

        if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
            if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
                // 1、把老的children清空
                unmountChildren(oldVnodeChildren);
            }
            // 2、只要新旧节点都不一样，新节点就替换旧节点
            if (oldVnodeChildren !== newVnodeChildren) {
                hostSetElementText(container, newVnodeChildren);
            }
        } else {
            // 文字转成数组的情况
            if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
                hostSetElementText(container, "");
                mountChildren(newVnodeChildren, container, parentComponent, anchor)
            } else {
                // 数组转数组，需要diff算法
                patchKeyedChildren(oldVnodeChildren, newVnodeChildren, container, parentComponent, anchor);
            }
        }

    }

    function patchKeyedChildren(oldVnodeChildren, newVnodeChildren, container, parentComponent, parentAnchor) {
        let i = 0;
        let e1 = oldVnodeChildren.length - 1;
        let e2 = newVnodeChildren.length - 1;

        function isSameVNodeType(n1, n2) {
            // 通过type和key判断是否是同一个节点
            return n1.type === n2.type && n1.key === n2.key;
        }

        // 索引不能大于新节点的长度以及老节点的长度，判断左侧对比的情况
        while (i <= e1 && i <= e2) {
            const n1 = oldVnodeChildren[i];
            const n2 = newVnodeChildren[i];

            if (isSameVNodeType(n1, n2)) {
                patch(n1, n2, container, parentComponent, parentAnchor);
            } else {
                break;
            }

            i++;
        }

        // 判断右侧对比的情况
        while (i <= e1 && i <= e2) {
            const n1 = oldVnodeChildren[e1];
            const n2 = newVnodeChildren[e2];

            if (isSameVNodeType(n1, n2)) {
                patch(n1, n2, container, parentComponent, parentAnchor);
            } else {
                break;
            }

            e1--;
            e2--;
        }

        // 判断新老节点的长度对比
        if (i > e1) {
            if (i <= e2) {
                // 如果是这种情况的话就说明 e2 也就是新节点的数量大于旧节点的数量
                // 也就是说新增了 vnode
                // 应该循环 c2
                // 锚点的计算：新的节点有可能需要添加到尾部，也可能添加到头部，所以需要指定添加的问题
                // 要添加的位置是当前的位置(e2 开始)+1
                // 因为对于往左侧添加的话，应该获取到 c2 的第一个元素
                // 所以我们需要从 e2 + 1 取到锚点的位置
                const nextPos = e2 + 1;
                const anchor = nextPos < newVnodeChildren.length ? newVnodeChildren[nextPos].el : parentAnchor;
                // 新节点还有剩余，需要新增节点
                while (i <= e2) {
                    // n2新增的节点
                    patch(null, newVnodeChildren[i], container, parentComponent, anchor);
                    i++;
                }
            }
        } else if (i > e2) {
            // 这种情况的话说明新节点的数量是小于旧节点的数量的
            // 那么我们就需要把多余的
            while (i <= e1) {
                hostRemove(oldVnodeChildren[i].el);
                i++;
            }
        } else {
            // 左右两边都比对完了，然后剩下的就是中间部位顺序变动的
            // 例如下面的情况
            // a,b,[c,d,e],f,g
            // a,b,[e,c,d],f,g

            let s1 = i;
            let s2 = i;
            const keyToNewIndexMap = new Map();
            let moved = false;
            let maxNewIndexSoFar = 0;


            // 先把 key 和 newIndex 绑定好，方便后续基于 key 找到 newIndex
            // 时间复杂度是 O(1)
            for (let i = s2; i <= e2; i++) {
                const nextChild = newVnodeChildren[i];
                keyToNewIndexMap.set(nextChild.key, i);
            }

            // 需要处理新节点的数量
            const toBePatched = e2 - s2 + 1;
            let patched = 0;

            // 初始化 从新的index映射为老的index
            // 创建数组的时候给定数组的长度，这个是性能最快的写法
            const newIndexToOldIndexMap = new Array(toBePatched);
            // 初始化为 0 , 后面处理的时候 如果发现是 0 的话，那么就说明新值在老的里面不存在
            for (let i = 0; i < toBePatched; i++) {
                newIndexToOldIndexMap[i] = 0;
            }

            // 遍历老节点
            // 1. 需要找出老节点有，而新节点没有的 -> 需要把这个节点删除掉
            // 2. 新老节点都有的，—> 需要 patch
            for (let i = s1; i <= e1; i++) {
                const prevChild = oldVnodeChildren[i];

                // 优化点
                // 如果老的节点大于新节点的数量的话，那么这里在处理老节点的时候就直接删除即可
                if (patched >= toBePatched) {
                    hostRemove(prevChild.el);
                    continue;
                }

                // null || undefined
                let newIndex;
                if (prevChild.key !== null) {
                    // 这里就可以通过key快速的查找了， 看看在新的里面这个节点存在不存在
                    // 时间复杂度O(1)
                    newIndex = keyToNewIndexMap.get(prevChild.key);
                } else {
                    // 如果没key 的话，那么只能是遍历所有的新节点来确定当前节点存在不存在了
                    // 时间复杂度O(n)
                    for (let j = s2; j <= e2; j++) {
                        if (isSameVNodeType(prevChild, newVnodeChildren[j])) {
                            newIndex = j;
                            break;
                        }
                    }
                }

                // 因为有可能 nextIndex 的值为0（0也是正常值）
                // 所以需要通过值是不是 undefined 或者 null 来判断
                if (newIndex === undefined) {
                    // 当前节点的key 不存在于 newChildren 中，需要把当前节点给删除掉
                    hostRemove(prevChild.el);
                } else {
                    // 新老节点都存在
                    console.log("新老节点都存在");
                    // 把新节点的索引和老的节点的索引建立映射关系
                    // i + 1 是因为 i 有可能是0 (0 的话会被认为新节点在老的节点中不存在)
                    newIndexToOldIndexMap[newIndex - s2] = i + 1;
                    // 来确定中间的节点是不是需要移动
                    // 新的 newIndex 如果一直是升序的话，那么就说明没有移动
                    // 所以我们可以记录最后一个节点在新的里面的索引，然后看看是不是升序
                    // 不是升序的话，我们就可以确定节点移动过了
                    if (newIndex >= maxNewIndexSoFar) {
                        maxNewIndexSoFar = newIndex;
                    } else {
                        moved = true;
                    }

                    patch(prevChild, newVnodeChildren[newIndex], container, parentComponent, null);
                    patched++;
                }
            }

            // 利用最长递增子序列来优化移动逻辑
            // 因为元素是升序的话，那么这些元素就是不需要移动的
            // 而我们就可以通过最长递增子序列来获取到升序的列表
            // 在移动的时候我们去对比这个列表，如果对比上的话，就说明当前元素不需要移动
            // 通过 moved 来进行优化，如果没有移动过的话 那么就不需要执行算法
            // getSequence 返回的是 newIndexToOldIndexMap 的索引值
            // 所以后面我们可以直接遍历索引值来处理，也就是直接使用 toBePatched 即可
            const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
            let j = increasingNewIndexSequence.length;

            // 遍历新节点
            // 1. 需要找出老节点没有，而新节点有的 -> 需要把这个节点创建
            // 2. 最后需要移动一下位置，比如 [c,d,e] -> [e,c,d]

            // 这里倒循环是因为在 insert 的时候，需要保证锚点是处理完的节点（也就是已经确定位置了）
            // 因为 insert 逻辑是使用的 insertBefore()
            for (let i = toBePatched - 1; i >= 0; i--) {
                // 确定当前要处理的节点索引
                const nextIndex = i + s2;
                const nextChild = newVnodeChildren[nextIndex];

                // 锚点等于当前节点索引+1
                // 也就是当前节点的后面一个节点(又因为是倒遍历，所以锚点是位置确定的节点)
                const anchor = nextIndex + 1 < newVnodeChildren.length ? newVnodeChildren[nextIndex + 1].el : null;

                if (newIndexToOldIndexMap[i] == 0) {
                    // 说明新节点在老的里面不存在
                    // 需要创建
                    patch(null, nextChild, container, parentComponent, anchor);
                } else if (moved) {
                    // 需要移动
                    // 1. j 已经没有了 说明剩下的都需要移动了
                    // 2. 最长子序列里面的值和当前的值匹配不上， 说明当前元素需要移动
                    if (j < 0 || i !== increasingNewIndexSequence[j]) {
                        // 移动的话使用 insert 即可
                        hostInsert(nextChild.el, container, anchor);
                    } else {
                        // 这里就是命中了  index 和 最长递增子序列的值
                        // 所以可以移动指针了
                        j--;
                    }
                }
            }
        }
    }

    function unmountChildren(children) {
        for (let i = 0; i < children.length; i++) {
            const el = children[i].el;
            hostRemove(el);
        }
    }

    function patchProps(el, oldProps, newProps) {
        if (oldProps !== newProps) {
            for (const key in newProps) {
                const prevProp = oldProps[key];
                const nextProp = newProps[key];

                if (prevProp !== nextProp) {
                    hostPatchProp(el, key, prevProp, nextProp);
                }
            }

            if (oldProps !== EMPTY_OBJ) {
                // 删除属性的操作
                for (const key in oldProps) {
                    if (!(key in newProps)) {
                        hostPatchProp(el, key, oldProps[key], null);
                    }
                }
            }

        }
    }

    function mountElement(vnode, container, parentComponent, anchor) {
        const el = (vnode.el = hostCreateElement(vnode.type));

        // string | array
        const { children, shapeFlag } = vnode

        if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
            el.textContent = children;
        } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            // vnode has children
            mountChildren(vnode.children, el, parentComponent, anchor)
        }

        // 设置html属性
        const { props } = vnode;
        if (props) {
            for (const key in props) {
                const val = props[key];
                hostPatchProp(el, key, null, val);
            }
        }

        // container.append(el);
        hostInsert(el, container, anchor)
    }

    function mountChildren(children, container, parentComponentm, anchor) {
        children.forEach(child => {
            patch(null, child, container, parentComponentm, anchor);
        });
    }

    function processComponent(oldVnode, newVnode, container, parentComponent, anchor) {
        if (!oldVnode) {
            // 挂载组件
            mountComponent(newVnode, container, parentComponent, anchor);
        } else {
            // 更新组件
            updateComponent(oldVnode, newVnode);
        }
    }

    // 挂载组件
    function mountComponent(initialVNode, container, parentComponent, anchor) {
        const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
        setupComponent(instance);
        setupRenderEffect(instance, initialVNode, container, anchor);
    }

    // 更新组件
    function updateComponent(oldVnode, newVnode) {
        const instance = (newVnode.component = oldVnode.component);
        if (shouldUpdateComponent(oldVnode, newVnode)) {
            instance.next = newVnode;
            instance.update();
        } else {
            newVnode.el = oldVnode.el;
            instance.vnode = newVnode;
        }
    }

    function setupRenderEffect(instance: any, initialVNode, container, anchor) {
        // 收集依赖
        instance.update = effect(() => {
            if (!instance.isMounted) {
                const { proxy } = instance;
                const subTree = (instance.subTree = instance.render.call(proxy, proxy));
                console.log("subTree", subTree);

                patch(null, subTree, container, instance, anchor);

                // element -> mount
                // 挂载根节点
                initialVNode.el = subTree.el

                instance.isMounted = true;
            } else {
                // 需要一个更新后的新的虚拟节点
                const { next, vnode } = instance;
                if (next) {
                    next.el = vnode.el
                    updateComponentPreRender(instance, next);
                }

                // 更新dom操作
                const { proxy } = instance;
                // 当前更新的subTree
                const subTree = instance.render.call(proxy, proxy);
                // 拿到上一次的subTree
                const prevSubTree = instance.subTree;
                instance.subTree = subTree;

                patch(prevSubTree, subTree, container, instance, anchor);
            }
        }, {
            scheduler() {
                console.log("update scheduler");
                queueJobs(instance.update);
            }
        })
    }

    return {
        createApp: createAppAPI(render)
    }
}

function updateComponentPreRender(instance, nextVNode) {
    instance.vnode = nextVNode;
    instance.next = null;
    instance.props = nextVNode.props;
}

function getSequence(arr: number[]): number[] {
    const p = arr.slice();
    const result = [0];
    let i, j, u, v, c;
    const len = arr.length;
    for (i = 0; i < len; i++) {
        const arrI = arr[i];
        if (arrI !== 0) {
            j = result[result.length - 1];
            if (arr[j] < arrI) {
                p[i] = j;
                result.push(i);
                continue;
            }
            u = 0;
            v = result.length - 1;
            while (u < v) {
                c = (u + v) >> 1;
                if (arr[result[c]] < arrI) {
                    u = c + 1;
                } else {
                    v = c;
                }
            }
            if (arrI < arr[result[u]]) {
                if (u > 0) {
                    p[i] = result[u - 1];
                }
                result[u] = i;
            }
        }
    }
    u = result.length;
    v = result[u - 1];
    while (u-- > 0) {
        result[u] = v;
        v = p[v];
    }
    return result;
}

